﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Utilities
{
    /// <summary>
    /// The compiled data of the template
    /// </summary>
    public class TemplateData
    {
        private TemplateData(LinkedList<INode> nodes)
        {
            _nodes = nodes;
        }

        private enum States
        {
            Text = 0,
            Value = 1
        }

        public static TemplateData Compile(string text, char startDelimiter, char endDelimiter)
        {
            States state = States.Text;
            LinkedList<INode> nodes = new LinkedList<INode>();
            TextNode textNode = new TextNode(); // the current text node being processed
            ValueNode valueNode = null; // The current value node being processed
            StringBuilder builder = new StringBuilder();

            for (int i = 0; i < text.Length; ++i)
            {
                char c = text[i];

                if (state == States.Text)
                {
                    if (c == startDelimiter)
                    {
                        if (text[i + 1] == startDelimiter) // Double character escapes the delimiter
                        {
                            builder.Append(c);
                            ++i; // Move to the next token
                            continue;
                        }

                        if (builder.Length > 0)
                        {
                            textNode.Text = builder.ToString();
                            builder.Clear();
                            nodes.AddLast(textNode);
                        }
                        // else { // Nothing to do }

                        valueNode = new ValueNode();
                        state = States.Value;
                    }
                    else // Not start delimiter
                    {
                        if (c == endDelimiter)
                        {
                            if (text[i + 1] == endDelimiter) // Double character escapes the delimiter
                            {
                                builder.Append(c);
                                ++i; // Move to the next token
                                continue;
                            }
                            else // Not a escaped end delimiter but a misplaced end delimiter
                            {
                                throw new InvalidOperationException("Misplaced end delimiter at character: " + i);
                            }
                        }
                        else
                        {
                            builder.Append(c);
                        }
                    }
                }
                else // state == States.Value
                {
                    if (c == endDelimiter)
                    {
                        if (builder.Length > 0)
                        {
                            valueNode.Name = builder.ToString();
                            builder.Clear();
                            nodes.AddLast(valueNode);
                        }

                        textNode = new TextNode();
                        state = States.Text;
                    }
                    else
                    {
                        if (c == startDelimiter)
                        {
                            throw new InvalidOperationException("Misplaced start delimiter at character: " + i);
                        }
                        else
                        {
                            builder.Append(c);
                        }
                    }
                }
            }

            // Flush out the last chunk
            if (builder.Length > 0)
            {
                textNode.Text = builder.ToString();
                builder.Clear();
                nodes.AddLast(textNode);
            }

            return new TemplateData(nodes);
        }

        public string Apply(IDictionary<string, object> values)
        {
            if (!_nodes.Any()) // Empty string
            {
                return string.Empty;
            }

            StringBuilder builder = new StringBuilder();

            foreach(INode node in _nodes)
            {
                if (node is TextNode)
                {
                    TextNode textNode = (TextNode)node;
                    builder.Append(textNode.Text);
                }
                else // It is a value node
                {
                    ValueNode valueNode = (ValueNode)node;

                    if (values == null
                        || !values.ContainsKey(valueNode.Name)) // No values were provided
                    {
                        builder.Append("[");
                        builder.Append(valueNode.Name);
                        builder.Append("]");
                    }
                    else
                    {
                        builder.Append(values[valueNode.Name]);
                    }
                }
            }

            return builder.ToString();
        }

        private interface INode
        {
        };

        private class TextNode : INode
        {
            public string Text;
        }

        private class ValueNode : INode
        {
            public string Name;
        }

        private LinkedList<INode> _nodes = new LinkedList<INode>();
    }
}
